// SPDX-License-Identifier: MIT
// Copyright (C) 2018-present iced project and contributors

package com.github.icedland.iced.x86.enc;

import com.github.icedland.iced.x86.CodeSize;
import com.github.icedland.iced.x86.CodeWriter;
import com.github.icedland.iced.x86.ConstantOffsets;
import com.github.icedland.iced.x86.Instruction;
import com.github.icedland.iced.x86.MemorySize;
import com.github.icedland.iced.x86.OpKind;
import com.github.icedland.iced.x86.Register;
import com.github.icedland.iced.x86.enc.OpCodeHandler.TryConvertToDisp8N;
import com.github.icedland.iced.x86.internal.IcedConstants;
import com.github.icedland.iced.x86.internal.enc.DisplSize;
import com.github.icedland.iced.x86.internal.enc.EncFlags3;
import com.github.icedland.iced.x86.internal.enc.EncoderFlags;
import com.github.icedland.iced.x86.internal.enc.ImmSize;

/**
 * Encodes instructions decoded by the {@link com.github.icedland.iced.x86.dec.Decoder} or instructions created by other code.
 * <p>
 * See also {@link BlockEncoder} which can encode any number of instructions.
 */
public final class Encoder {
	// GENERATOR-BEGIN: ImmSizes
	// ⚠️This was generated by GENERATOR!🦹‍♂️
	private static final int[] s_immSizes = new int[] {
		0,// NONE
		1,// SIZE1
		2,// SIZE2
		4,// SIZE4
		8,// SIZE8
		3,// SIZE2_1
		2,// SIZE1_1
		4,// SIZE2_2
		6,// SIZE4_2
		1,// RIP_REL_SIZE1_TARGET16
		1,// RIP_REL_SIZE1_TARGET32
		1,// RIP_REL_SIZE1_TARGET64
		2,// RIP_REL_SIZE2_TARGET16
		2,// RIP_REL_SIZE2_TARGET32
		2,// RIP_REL_SIZE2_TARGET64
		4,// RIP_REL_SIZE4_TARGET32
		4,// RIP_REL_SIZE4_TARGET64
		1,// SIZE_IB_REG
		1,// SIZE1_OP_CODE
	};
	// GENERATOR-END: ImmSizes

	/**
	 * Disables 2-byte VEX encoding and encodes all VEX instructions with the 3-byte VEX encoding
	 */
	public boolean getPreventVEX2() {
		return internal_PreventVEX2 != 0;
	}

	/**
	 * Disables 2-byte VEX encoding and encodes all VEX instructions with the 3-byte VEX encoding
	 */
	public void setPreventVEX2(boolean value) {
		internal_PreventVEX2 = value ? 0xFFFF_FFFF : 0;
	}

	int internal_PreventVEX2;

	/**
	 * Value of the {@code VEX.W} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public int getVEX_WIG() {
		return (internal_VEX_WIG_LIG >>> 7) & 1;
	}

	/**
	 * Value of the {@code VEX.W} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public void setVEX_WIG(int value) {
		internal_VEX_WIG_LIG = (internal_VEX_WIG_LIG & ~0x80) | ((value & 1) << 7);
	}

	int internal_VEX_WIG_LIG;
	int internal_VEX_LIG;

	/**
	 * Value of the {@code VEX.L} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public int getVEX_LIG() {
		return (internal_VEX_WIG_LIG >>> 2) & 1;
	}

	/**
	 * Value of the {@code VEX.L} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public void setVEX_LIG(int value) {
		internal_VEX_WIG_LIG = (internal_VEX_WIG_LIG & ~4) | ((value & 1) << 2);
		internal_VEX_LIG = (value & 1) << 2;
	}

	/**
	 * Value of the {@code EVEX.W} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public int getEVEX_WIG() {
		return internal_EVEX_WIG >>> 7;
	}

	/**
	 * Value of the {@code EVEX.W} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public void setEVEX_WIG(int value) {
		internal_EVEX_WIG = (value & 1) << 7;
	}

	int internal_EVEX_WIG;

	/**
	 * Value of the {@code EVEX.L'L} bits to use if it's an instruction that ignores the bits. Default is 0.
	 */
	public int getEVEX_LIG() {
		return internal_EVEX_LIG >>> 5;
	}

	/**
	 * Value of the {@code EVEX.L'L} bits to use if it's an instruction that ignores the bits. Default is 0.
	 */
	public void setEVEX_LIG(int value) {
		internal_EVEX_LIG = (value & 3) << 5;
	}

	int internal_EVEX_LIG;

	/**
	 * Value of the {@code MVEX.W} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public int getMVEX_WIG() {
		return internal_MVEX_WIG >>> 7;
	}

	/**
	 * Value of the {@code MVEX.W} bit to use if it's an instruction that ignores the bit. Default is 0.
	 */
	public void setMVEX_WIG(int value) {
		internal_MVEX_WIG = (value & 1) << 7;
	}

	int internal_MVEX_WIG;

	static final String ERROR_ONLY_1632_BIT_MODE = "The instruction can only be used in 16/32-bit mode";
	static final String ERROR_ONLY_64_BIT_MODE = "The instruction can only be used in 64-bit mode";

	private final CodeWriter writer;
	private final int bitness;
	private final OpCodeHandler[] handlers;

	private final int[] immSizes;
	private long currentRip;
	private String errorMessage;
	private OpCodeHandler handler;
	private int eip;
	private int displAddr;
	private int immAddr;
	int immediate;
	// high 32 bits if it's a 64-bit immediate
	// high 32 bits if it's an IP relative immediate (jcc,call target)
	// high 32 bits if it's a 64-bit absolute address
	int immediateHi;
	private int displ;
	// high 32 bits if it's an IP relative mem displ (target)
	private int displHi;
	private final int opSize16Flags;
	private final int opSize32Flags;
	private final int adrSize16Flags;
	private final int adrSize32Flags;
	int opCode;
	int encoderFlags;
	private int displSize;
	int immSize;
	private byte modRM;
	private byte sib;

	/**
	 * Gets the bitness (16, 32 or 64)
	 */
	public int getBitness() {
		return bitness;
	}

	/**
	 * Creates an encoder
	 *
	 * @param bitness 16, 32 or 64
	 * @param writer  Destination
	 */
	public Encoder(int bitness, CodeWriter writer) {
		if (!(bitness == 16 || bitness == 32 || bitness == 64))
			throw new IllegalArgumentException("bitness");
		if (writer == null)
			throw new NullPointerException("writer");
		immSizes = s_immSizes;
		this.writer = writer;
		this.bitness = bitness;
		handlers = InternalEncoderOpCodeHandlers.handlers;
		handler = null; // It's initialized by tryEncode
		opSize16Flags = bitness != 16 ? EncoderFlags.P66 : 0;
		opSize32Flags = bitness == 16 ? EncoderFlags.P66 : 0;
		adrSize16Flags = bitness != 16 ? EncoderFlags.P67 : 0;
		adrSize32Flags = bitness != 32 ? EncoderFlags.P67 : 0;
	}

	/**
	 * Encodes an instruction and returns the size of the encoded instruction.
	 * <p>
	 * An {@link EncoderException} is thrown if it failed to encode the instruction.
	 *
	 * @param instruction Instruction to encode
	 * @param rip         RIP of the encoded instruction
	 * @throws EncoderException If it fails to encode an instruction
	 * @see #tryEncode(Instruction, long)
	 */
	public int encode(Instruction instruction, long rip) {
		Object result = tryEncode(instruction, rip);
		if (result instanceof Integer)
			return ((Integer)result).intValue();
		if (result instanceof String)
			throw new EncoderException((String)result, instruction);
		throw new UnsupportedOperationException();
	}

	/**
	 * Tries to encode an instruction. If successful, it will return an {@link Integer} with the length of the
	 * instruction, else it will return a {@link String} with the error message.
	 *
	 * @param instruction Instruction to encode
	 * @param rip         {@code RIP} of the encoded instruction
	 * @return An {@link Integer} (on success) or a {@link String} (on failure)
	 * @see #encode(Instruction, long)
	 */
	@SuppressWarnings("deprecation")
	public Object tryEncode(Instruction instruction, long rip) {
		currentRip = rip;
		eip = (int)rip;
		this.errorMessage = null;
		encoderFlags = EncoderFlags.NONE;
		displSize = DisplSize.NONE;
		immSize = ImmSize.NONE;
		modRM = 0;

		OpCodeHandler handler = handlers[instruction.getCode()];
		this.handler = handler;
		opCode = handler.opCode;
		if (handler.groupIndex >= 0) {
			assert encoderFlags == 0 : encoderFlags;
			encoderFlags = EncoderFlags.MOD_RM;
			modRM = (byte)(handler.groupIndex << 3);
		}
		if (handler.rmGroupIndex >= 0) {
			assert encoderFlags == 0 || encoderFlags == EncoderFlags.MOD_RM : encoderFlags;
			encoderFlags = EncoderFlags.MOD_RM;
			modRM |= (byte)(handler.rmGroupIndex | 0xC0);
		}

		switch (handler.encFlags3 & (EncFlags3.BIT16OR32 | EncFlags3.BIT64)) {
		case EncFlags3.BIT16OR32 | EncFlags3.BIT64:
			break;

		case EncFlags3.BIT16OR32:
			if (bitness == 64)
				setErrorMessage(ERROR_ONLY_1632_BIT_MODE);
			break;

		case EncFlags3.BIT64:
			if (bitness != 64)
				setErrorMessage(ERROR_ONLY_64_BIT_MODE);
			break;

		default:
			throw new UnsupportedOperationException();
		}

		switch (handler.opSize) {
		case CodeSize.UNKNOWN:
			break;

		case CodeSize.CODE16:
			encoderFlags |= opSize16Flags;
			break;

		case CodeSize.CODE32:
			encoderFlags |= opSize32Flags;
			break;

		case CodeSize.CODE64:
			if ((handler.encFlags3 & EncFlags3.DEFAULT_OP_SIZE64) == 0)
				encoderFlags |= EncoderFlags.W;
			break;

		default:
			throw new UnsupportedOperationException();
		}

		switch (handler.addrSize) {
		case CodeSize.UNKNOWN:
			break;

		case CodeSize.CODE16:
			encoderFlags |= adrSize16Flags;
			break;

		case CodeSize.CODE32:
			encoderFlags |= adrSize32Flags;
			break;

		case CodeSize.CODE64:
			break;

		default:
			throw new UnsupportedOperationException();
		}

		if (!handler.isSpecialInstr) {
			Op[] ops = handler.operands;
			for (int i = 0; i < ops.length; i++)
				ops[i].encode(this, instruction, i);

			if ((handler.encFlags3 & EncFlags3.FWAIT) != 0)
				writeByteInternal(0x9B);

			handler.encode(this, instruction);

			int opCode = this.opCode;
			if (!handler.is2ByteOpCode)
				writeByteInternal(opCode);
			else {
				writeByteInternal(opCode >>> 8);
				writeByteInternal(opCode);
			}

			if ((encoderFlags & (EncoderFlags.MOD_RM | EncoderFlags.DISPL)) != 0)
				writeModRM();

			if (immSize != ImmSize.NONE)
				writeImmediate();
		}
		else {
			assert handler instanceof InternalOpCodeHandlers.DeclareDataHandler || handler instanceof InternalOpCodeHandlers.ZeroBytesHandler;
			handler.encode(this, instruction);
		}

		int instrLen = (int)currentRip - (int)rip;
		if (instrLen > IcedConstants.MAX_INSTRUCTION_LENGTH && !handler.isSpecialInstr)
			setErrorMessage(String.format("Instruction length > %d bytes", IcedConstants.MAX_INSTRUCTION_LENGTH));
		String errorMessage = this.errorMessage;
		if (errorMessage != null)
			return errorMessage;
		return instrLen;
	}

	void setErrorMessage(String msg) {
		if (errorMessage == null)
			errorMessage = msg;
	}

	boolean verifyOpKind(int operand, int expected, int actual) {
		if (expected == actual)
			return true;
		setErrorMessage(String.format("Operand %d: Expected OpKind: %d, actual OpKind: %d", operand, expected, actual));
		return false;
	}

	boolean verifyRegister(int operand, int expected, int actual) {
		if (expected == actual)
			return true;
		setErrorMessage(String.format("Operand %d: Expected Register: %d, actual Register: %d", operand, expected, actual));
		return false;
	}

	boolean verify(int operand, int register, int regLo, int regHi) {
		if (bitness != 64 && regHi > regLo + 7)
			regHi = regLo + 7;
		if (regLo <= register && register <= regHi)
			return true;
		setErrorMessage(String.format("Operand %d: Register %d != between %d and %d (inclusive)", operand, register, regLo, regHi));
		return false;
	}

	void addBranch(int opKind, int immSize, Instruction instruction, int operand) {
		if (!verifyOpKind(operand, opKind, instruction.getOpKind(operand)))
			return;

		long target;
		switch (immSize) {
		case 1:
			switch (opKind) {
			case OpKind.NEAR_BRANCH16:
				encoderFlags |= opSize16Flags;
				this.immSize = ImmSize.RIP_REL_SIZE1_TARGET16;
				immediate = instruction.getNearBranch16();
				break;

			case OpKind.NEAR_BRANCH32:
				encoderFlags |= opSize32Flags;
				this.immSize = ImmSize.RIP_REL_SIZE1_TARGET32;
				immediate = instruction.getNearBranch32();
				break;

			case OpKind.NEAR_BRANCH64:
				this.immSize = ImmSize.RIP_REL_SIZE1_TARGET64;
				target = instruction.getNearBranch64();
				immediate = (int)target;
				immediateHi = (int)(target >>> 32);
				break;

			default:
				throw new UnsupportedOperationException();
			}
			break;

		case 2:
			switch (opKind) {
			case OpKind.NEAR_BRANCH16:
				encoderFlags |= opSize16Flags;
				this.immSize = ImmSize.RIP_REL_SIZE2_TARGET16;
				immediate = instruction.getNearBranch16();
				break;

			default:
				throw new UnsupportedOperationException();
			}
			break;

		case 4:
			switch (opKind) {
			case OpKind.NEAR_BRANCH32:
				encoderFlags |= opSize32Flags;
				this.immSize = ImmSize.RIP_REL_SIZE4_TARGET32;
				immediate = instruction.getNearBranch32();
				break;

			case OpKind.NEAR_BRANCH64:
				this.immSize = ImmSize.RIP_REL_SIZE4_TARGET64;
				target = instruction.getNearBranch64();
				immediate = (int)target;
				immediateHi = (int)(target >>> 32);
				break;

			default:
				throw new UnsupportedOperationException();
			}
			break;

		default:
			throw new UnsupportedOperationException();
		}
	}

	void addBranchX(int immSize, Instruction instruction, int operand) {
		if (bitness == 64) {
			if (!verifyOpKind(operand, OpKind.NEAR_BRANCH64, instruction.getOpKind(operand)))
				return;

			long target = instruction.getNearBranch64();
			switch (immSize) {
			case 2:
				encoderFlags |= EncoderFlags.P66;
				this.immSize = ImmSize.RIP_REL_SIZE2_TARGET64;
				immediate = (int)target;
				immediateHi = (int)(target >>> 32);
				break;

			case 4:
				this.immSize = ImmSize.RIP_REL_SIZE4_TARGET64;
				immediate = (int)target;
				immediateHi = (int)(target >>> 32);
				break;

			default:
				throw new UnsupportedOperationException();
			}
		}
		else {
			assert bitness == 16 || bitness == 32 : bitness;
			if (!verifyOpKind(operand, OpKind.NEAR_BRANCH32, instruction.getOpKind(operand)))
				return;

			switch (immSize) {
			case 2:
				encoderFlags |= (bitness & 0x20) << 2;
				this.immSize = ImmSize.RIP_REL_SIZE2_TARGET32;
				immediate = instruction.getNearBranch32();
				break;

			case 4:
				encoderFlags |= (bitness & 0x10) << 3;
				this.immSize = ImmSize.RIP_REL_SIZE4_TARGET32;
				immediate = instruction.getNearBranch32();
				break;

			case 8:
			default:
				throw new UnsupportedOperationException();
			}
		}
	}

	void addBranchDisp(int displSize, Instruction instruction, int operand) {
		assert displSize == 2 || displSize == 4 : displSize;
		int opKind;
		switch (displSize) {
		case 2:
			opKind = OpKind.NEAR_BRANCH16;
			immSize = ImmSize.SIZE2;
			immediate = instruction.getNearBranch16();
			break;

		case 4:
			opKind = OpKind.NEAR_BRANCH32;
			immSize = ImmSize.SIZE4;
			immediate = instruction.getNearBranch32();
			break;

		default:
			throw new UnsupportedOperationException();
		}
		if (!verifyOpKind(operand, opKind, instruction.getOpKind(operand)))
			return;
	}

	void addFarBranch(Instruction instruction, int operand, int size) {
		if (size == 2) {
			if (!verifyOpKind(operand, OpKind.FAR_BRANCH16, instruction.getOpKind(operand)))
				return;
			immSize = ImmSize.SIZE2_2;
			immediate = instruction.getFarBranch16();
			immediateHi = instruction.getFarBranchSelector();
		}
		else {
			assert size == 4 : size;
			if (!verifyOpKind(operand, OpKind.FAR_BRANCH32, instruction.getOpKind(operand)))
				return;
			immSize = ImmSize.SIZE4_2;
			immediate = instruction.getFarBranch32();
			immediateHi = instruction.getFarBranchSelector();
		}
		if (bitness != size * 8)
			encoderFlags |= EncoderFlags.P66;
	}

	void setAddrSize(int regSize) {
		assert regSize == 2 || regSize == 4 || regSize == 8 : regSize;
		if (bitness == 64) {
			if (regSize == 2) {
				setErrorMessage(String.format("Invalid register size: %d, must be 32-bit or 64-bit", regSize * 8));
			}
			else if (regSize == 4)
				encoderFlags |= EncoderFlags.P67;
		}
		else {
			if (regSize == 8) {
				setErrorMessage(String.format("Invalid register size: %d, must be 16-bit or 32-bit", regSize * 8));
			}
			else if (bitness == 16) {
				if (regSize == 4)
					encoderFlags |= EncoderFlags.P67;
			}
			else {
				assert bitness == 32 : bitness;
				if (regSize == 2)
					encoderFlags |= EncoderFlags.P67;
			}
		}
	}

	void addAbsMem(Instruction instruction, int operand) {
		encoderFlags |= EncoderFlags.DISPL;
		int opKind = instruction.getOpKind(operand);
		if (opKind == OpKind.MEMORY) {
			if (instruction.getMemoryBase() != Register.NONE || instruction.getMemoryIndex() != Register.NONE) {
				setErrorMessage(String.format("Operand %d: Absolute addresses can't have base and/or index regs", operand));
				return;
			}
			if (instruction.getMemoryIndexScale() != 1) {
				setErrorMessage(String.format("Operand %d: Absolute addresses must have scale == *1", operand));
				return;
			}
			switch (instruction.getMemoryDisplSize()) {
			case 2:
				if (bitness == 64) {
					setErrorMessage(String.format("Operand %d: 16-bit abs addresses can't be used in 64-bit mode", operand));
					return;
				}
				if (bitness == 32)
					encoderFlags |= EncoderFlags.P67;
				displSize = DisplSize.SIZE2;
				if (Long.compareUnsigned(instruction.getMemoryDisplacement64(), 0xFFFF) > 0) {
					setErrorMessage(String.format("Operand %d: Displacement must fit in a ushort", operand));
					return;
				}
				displ = instruction.getMemoryDisplacement32();
				break;
			case 4:
				encoderFlags |= adrSize32Flags;
				displSize = DisplSize.SIZE4;
				if (Long.compareUnsigned(instruction.getMemoryDisplacement64(), 0xFFFF_FFFFL) > 0) {
					setErrorMessage(String.format("Operand %d: Displacement must fit in a uint", operand));
					return;
				}
				displ = instruction.getMemoryDisplacement32();
				break;
			case 8:
				if (bitness != 64) {
					setErrorMessage(String.format("Operand %d: 64-bit abs address is only available in 64-bit mode", operand));
					return;
				}
				displSize = DisplSize.SIZE8;
				long addr = instruction.getMemoryDisplacement64();
				displ = (int)addr;
				displHi = (int)(addr >>> 32);
				break;
			default:
				setErrorMessage(String
						.format("Operand %d: Instruction.setMemoryDisplSize() must be initialized to 2 (16-bit), 4 (32-bit) or 8 (64-bit)", operand));
				break;
			}
		}
		else
			setErrorMessage(String.format("Operand %d: Expected OpKind.MEMORY, actual: %d", operand, opKind));
	}

	void addModRMRegister(Instruction instruction, int operand, int regLo, int regHi) {
		if (!verifyOpKind(operand, OpKind.REGISTER, instruction.getOpKind(operand)))
			return;
		int reg = instruction.getOpRegister(operand);
		if (!verify(operand, reg, regLo, regHi))
			return;
		int regNum = reg - regLo;
		if (regLo == Register.AL) {
			if (reg >= Register.SPL) {
				regNum -= 4;
				encoderFlags |= EncoderFlags.REX;
			}
			else if (reg >= Register.AH)
				encoderFlags |= EncoderFlags.HIGH_LEGACY_8_BIT_REGS;
		}
		assert Integer.compareUnsigned(regNum, 31) <= 0 : regNum;
		modRM |= (byte)((regNum & 7) << 3);
		encoderFlags |= EncoderFlags.MOD_RM;
		encoderFlags |= (regNum & 8) >>> 1;
		encoderFlags |= (regNum & 0x10) << (9 - 4);
	}

	void addReg(Instruction instruction, int operand, int regLo, int regHi) {
		if (!verifyOpKind(operand, OpKind.REGISTER, instruction.getOpKind(operand)))
			return;
		int reg = instruction.getOpRegister(operand);
		if (!verify(operand, reg, regLo, regHi))
			return;
		int regNum = reg - regLo;
		if (regLo == Register.AL) {
			if (reg >= Register.SPL) {
				regNum -= 4;
				encoderFlags |= EncoderFlags.REX;
			}
			else if (reg >= Register.AH)
				encoderFlags |= EncoderFlags.HIGH_LEGACY_8_BIT_REGS;
		}
		assert Integer.compareUnsigned(regNum, 15) <= 0 : regNum;
		opCode |= regNum & 7;
		assert Integer.compareUnsigned(regNum, 15) <= 0 : regNum;
		encoderFlags |= regNum >>> 3;// regNum <= 15, so no need to mask out anything
	}

	void addRegOrMem(Instruction instruction, int operand, int regLo, int regHi, boolean allowMemOp, boolean allowRegOp) {
		addRegOrMem(instruction, operand, regLo, regHi, Register.NONE, Register.NONE, allowMemOp, allowRegOp);
	}

	void addRegOrMem(Instruction instruction, int operand, int regLo, int regHi, int vsibIndexRegLo, int vsibIndexRegHi, boolean allowMemOp,
			boolean allowRegOp) {
		int opKind = instruction.getOpKind(operand);
		encoderFlags |= EncoderFlags.MOD_RM;
		if (opKind == OpKind.REGISTER) {
			if (!allowRegOp) {
				setErrorMessage(String.format("Operand %d: register operand != allowed", operand));
				return;
			}
			int reg = instruction.getOpRegister(operand);
			if (!verify(operand, reg, regLo, regHi))
				return;
			int regNum = reg - regLo;
			if (regLo == Register.AL) {
				if (reg >= Register.R8L)
					regNum -= 4;
				else if (reg >= Register.SPL) {
					regNum -= 4;
					encoderFlags |= EncoderFlags.REX;
				}
				else if (reg >= Register.AH)
					encoderFlags |= EncoderFlags.HIGH_LEGACY_8_BIT_REGS;
			}
			modRM |= (byte)(regNum & 7);
			modRM |= 0xC0;
			encoderFlags |= (regNum >>> 3) & 3;
			assert Integer.compareUnsigned(regNum, 31) <= 0 : regNum;
		}
		else if (opKind == OpKind.MEMORY) {
			if (!allowMemOp) {
				setErrorMessage(String.format("Operand %d: memory operand != allowed", operand));
				return;
			}
			if (MemorySize.isBroadcast(instruction.getMemorySize()))
				encoderFlags |= EncoderFlags.BROADCAST;

			int codeSize = instruction.getCodeSize();
			if (codeSize == CodeSize.UNKNOWN) {
				if (bitness == 64)
					codeSize = CodeSize.CODE64;
				else if (bitness == 32)
					codeSize = CodeSize.CODE32;
				else {
					assert bitness == 16 : bitness;
					codeSize = CodeSize.CODE16;
				}
			}
			@SuppressWarnings("deprecation")
			int addrSize = com.github.icedland.iced.x86.InternalInstructionUtils.getAddressSizeInBytes(instruction.getMemoryBase(),
					instruction.getMemoryIndex(), instruction.getMemoryDisplSize(), codeSize) * 8;
			if (addrSize != bitness)
				encoderFlags |= EncoderFlags.P67;
			if ((encoderFlags & EncoderFlags.REG_IS_MEMORY) != 0) {
				int regSize = getRegisterOpSize(instruction);
				if (regSize != addrSize) {
					setErrorMessage(String.format("Operand %d: Register operand size must equal memory addressing mode (16/32/64)", operand));
					return;
				}
			}
			if (addrSize == 16) {
				if (vsibIndexRegLo != Register.NONE) {
					setErrorMessage(
							String.format("Operand %d: VSIB operands can't use 16-bit addressing. It must be 32-bit or 64-bit addressing", operand));
					return;
				}
				addMemOp16(instruction, operand);
			}
			else
				addMemOp(instruction, operand, addrSize, vsibIndexRegLo, vsibIndexRegHi);
		}
		else
			setErrorMessage(String.format("Operand %d: Expected a register or memory operand, but opKind is %d", operand, opKind));
	}

	private static int getRegisterOpSize(Instruction instruction) {
		assert instruction.getOp0Kind() == OpKind.REGISTER : instruction.getOp0Kind();
		if (instruction.getOp0Kind() == OpKind.REGISTER) {
			int reg = instruction.getOp0Register();
			if (Register.isGPR64(reg))
				return 64;
			if (Register.isGPR32(reg))
				return 32;
			if (Register.isGPR16(reg))
				return 16;
		}
		return 0;
	}

	private Integer tryConvertToDisp8N(Instruction instruction, int displ) {
		TryConvertToDisp8N tryConvertToDisp8N = handler.tryConvertToDisp8N;
		if (tryConvertToDisp8N != null)
			return tryConvertToDisp8N.convert(this, handler, instruction, displ);
		if (-0x80 <= displ && displ <= 0x7F)
			return displ;
		return null;
	}

	private void addMemOp16(Instruction instruction, int operand) {
		if (bitness == 64) {
			setErrorMessage(String.format("Operand %d: 16-bit addressing can't be used by 64-bit code", operand));
			return;
		}
		int baseReg = instruction.getMemoryBase();
		int indexReg = instruction.getMemoryIndex();
		int displSize = instruction.getMemoryDisplSize();
		if (baseReg == Register.BX && indexReg == Register.SI) {
			// Nothing
		}
		else if (baseReg == Register.BX && indexReg == Register.DI)
			modRM |= 1;
		else if (baseReg == Register.BP && indexReg == Register.SI)
			modRM |= 2;
		else if (baseReg == Register.BP && indexReg == Register.DI)
			modRM |= 3;
		else if (baseReg == Register.SI && indexReg == Register.NONE)
			modRM |= 4;
		else if (baseReg == Register.DI && indexReg == Register.NONE)
			modRM |= 5;
		else if (baseReg == Register.BP && indexReg == Register.NONE)
			modRM |= 6;
		else if (baseReg == Register.BX && indexReg == Register.NONE)
			modRM |= 7;
		else if (baseReg == Register.NONE && indexReg == Register.NONE) {
			modRM |= 6;
			this.displSize = DisplSize.SIZE2;
			if (Long.compareUnsigned(instruction.getMemoryDisplacement64(), 0xFFFF) > 0) {
				setErrorMessage(String.format("Operand %d: Displacement must fit in a ushort", operand));
				return;
			}
			displ = instruction.getMemoryDisplacement32();
		}
		else {
			setErrorMessage(String.format("Operand %d: Invalid 16-bit base + index registers: base=%d, index=%d", operand, baseReg, indexReg));
			return;
		}

		if (baseReg != Register.NONE || indexReg != Register.NONE) {
			if (instruction.getMemoryDisplacement64() < -0x8000 || instruction.getMemoryDisplacement64() > 0xFFFF) {
				setErrorMessage(String.format("Operand %d: Displacement must fit in a short or a ushort", operand));
				return;
			}
			displ = instruction.getMemoryDisplacement32();
			// [bp] => [bp+00]
			if (displSize == 0 && baseReg == Register.BP && indexReg == Register.NONE) {
				displSize = 1;
				if (displ != 0) {
					setErrorMessage(String.format("Operand %d: Displacement must be 0 if displSize == 0", operand));
					return;
				}
			}
			if (displSize == 1) {
				Integer result = tryConvertToDisp8N(instruction, (short)displ);
				if (result != null)
					displ = result.intValue();
				else
					displSize = 2;
			}
			if (displSize == 0) {
				if (displ != 0) {
					setErrorMessage(String.format("Operand %d: Displacement must be 0 if displSize == 0", operand));
					return;
				}
			}
			else if (displSize == 1) {
				// This if check should never be true when we're here
				if ((int)displ < -0x80 || (int)displ > 0x7F) {
					setErrorMessage(String.format("Operand %d: Displacement must fit in a byte", operand));
					return;
				}
				modRM |= 0x40;
				this.displSize = DisplSize.SIZE1;
			}
			else if (displSize == 2) {
				modRM |= 0x80;
				this.displSize = DisplSize.SIZE2;
			}
			else {
				setErrorMessage(String.format("Operand %d: Invalid displacement size: %d, must be 0, 1, or 2", operand, displSize));
				return;
			}
		}
	}

	private void addMemOp(Instruction instruction, int operand, int addrSize, int vsibIndexRegLo, int vsibIndexRegHi) {
		assert addrSize == 32 || addrSize == 64 : addrSize;
		if (bitness != 64 && addrSize == 64) {
			setErrorMessage(String.format("Operand %d: 64-bit addressing can only be used in 64-bit mode", operand));
			return;
		}

		int baseReg = instruction.getMemoryBase();
		int indexReg = instruction.getMemoryIndex();
		int displSize = instruction.getMemoryDisplSize();

		int baseRegLo, baseRegHi;
		int indexRegLo, indexRegHi;
		if (addrSize == 64) {
			baseRegLo = Register.RAX;
			baseRegHi = Register.R15;
		}
		else {
			assert addrSize == 32 : addrSize;
			baseRegLo = Register.EAX;
			baseRegHi = Register.R15D;
		}
		if (vsibIndexRegLo != Register.NONE) {
			indexRegLo = vsibIndexRegLo;
			indexRegHi = vsibIndexRegHi;
		}
		else {
			indexRegLo = baseRegLo;
			indexRegHi = baseRegHi;
		}
		if (baseReg != Register.NONE && baseReg != Register.RIP && baseReg != Register.EIP && !verify(operand, baseReg, baseRegLo, baseRegHi))
			return;
		if (indexReg != Register.NONE && !verify(operand, indexReg, indexRegLo, indexRegHi))
			return;

		if (displSize != 0 && displSize != 1 && displSize != 4 && displSize != 8) {
			setErrorMessage(String.format("Operand %d: Invalid displ size: %d, must be 0, 1, 4, 8", operand, displSize));
			return;
		}
		if (baseReg == Register.RIP || baseReg == Register.EIP) {
			if (indexReg != Register.NONE) {
				setErrorMessage(String.format("Operand %d: RIP relative addressing can't use an index register", operand));
				return;
			}
			if (instruction.getRawMemoryIndexScale() != 0) {
				setErrorMessage(String.format("Operand %d: RIP relative addressing must use scale *1", operand));
				return;
			}
			if (bitness != 64) {
				setErrorMessage(String.format("Operand %d: RIP/EIP relative addressing is only available in 64-bit mode", operand));
				return;
			}
			if ((encoderFlags & EncoderFlags.MUST_USE_SIB) != 0) {
				setErrorMessage(String.format("Operand %d: RIP/EIP relative addressing isn't supported", operand));
				return;
			}
			modRM |= 5;
			long target = instruction.getMemoryDisplacement64();
			if (baseReg == Register.RIP) {
				this.displSize = DisplSize.RIP_REL_SIZE4_TARGET64;
				displ = (int)target;
				displHi = (int)(target >>> 32);
			}
			else {
				this.displSize = DisplSize.RIP_REL_SIZE4_TARGET32;
				if (Long.compareUnsigned(target, 0xFFFF_FFFFL) > 0) {
					setErrorMessage(String.format("Operand %d: Target address doesn't fit in 32 bits: 0x%X", operand, target));
					return;
				}
				displ = (int)target;
			}
			return;
		}
		int scale = instruction.getRawMemoryIndexScale();
		displ = instruction.getMemoryDisplacement32();
		if (addrSize == 64) {
			if (instruction.getMemoryDisplacement64() < -0x8000_0000 || instruction.getMemoryDisplacement64() > 0x7FFF_FFFF) {
				setErrorMessage(String.format("Operand %d: Displacement must fit in an int", operand));
				return;
			}
		}
		else {
			assert addrSize == 32 : addrSize;
			if (instruction.getMemoryDisplacement64() < -0x8000_0000 || instruction.getMemoryDisplacement64() > 0xFFFF_FFFFL) {
				setErrorMessage(String.format("Operand %d: Displacement must fit in an int or a uint", operand));
				return;
			}
		}
		if (baseReg == Register.NONE && indexReg == Register.NONE) {
			if (vsibIndexRegLo != Register.NONE) {
				setErrorMessage(String.format("Operand %d: VSIB addressing can't use an offset-only address", operand));
				return;
			}
			if (bitness == 64 || scale != 0 || (encoderFlags & EncoderFlags.MUST_USE_SIB) != 0) {
				modRM |= 4;
				this.displSize = DisplSize.SIZE4;
				encoderFlags |= EncoderFlags.SIB;
				sib = (byte)(0x25 | (scale << 6));
				return;
			}
			else {
				modRM |= 5;
				this.displSize = DisplSize.SIZE4;
				return;
			}
		}

		int baseNum = baseReg == Register.NONE ? -1 : baseReg - baseRegLo;
		int indexNum = indexReg == Register.NONE ? -1 : indexReg - indexRegLo;

		// [ebp]/[ebp+index*scale] => [ebp+00]/[ebp+index*scale+00]
		if (displSize == 0 && (baseNum & 7) == 5) {
			displSize = 1;
			if (displ != 0) {
				setErrorMessage(String.format("Operand %d: Displacement must be 0 if displSize == 0", operand));
				return;
			}
		}

		if (displSize == 1) {
			Integer result = tryConvertToDisp8N(instruction, (int)displ);
			if (result != null)
				displ = result.intValue();
			else
				displSize = addrSize / 8;
		}

		if (baseReg == Register.NONE) {
			// Tested earlier in the method
			assert indexReg != Register.NONE : indexReg;
			this.displSize = DisplSize.SIZE4;
		}
		else if (displSize == 1) {
			// This if check should never be true when we're here
			if ((int)displ < -0x80 || (int)displ > 0x7F) {
				setErrorMessage(String.format("Operand %d: Displacement must fit in a byte", operand));
				return;
			}
			modRM |= 0x40;
			this.displSize = DisplSize.SIZE1;
		}
		else if (displSize == addrSize / 8) {
			modRM |= 0x80;
			this.displSize = DisplSize.SIZE4;
		}
		else if (displSize == 0) {
			if (displ != 0) {
				setErrorMessage(String.format("Operand %d: Displacement must be 0 if displSize == 0", operand));
				return;
			}
		}
		else {
			setErrorMessage(String.format("Operand %d: Invalid Instruction.getMemoryDisplSize() value", operand));
			return;
		}

		if (indexReg == Register.NONE && (baseNum & 7) != 4 && scale == 0 && (encoderFlags & EncoderFlags.MUST_USE_SIB) == 0) {
			// Tested earlier in the method
			assert baseReg != Register.NONE : baseReg;
			modRM |= (byte)(baseNum & 7);
		}
		else {
			encoderFlags |= EncoderFlags.SIB;
			sib = (byte)(scale << 6);
			modRM |= 4;
			if (indexReg == Register.RSP || indexReg == Register.ESP) {
				setErrorMessage(String.format("Operand %d: ESP/RSP can't be used as an index register", operand));
				return;
			}
			if (baseNum < 0)
				sib |= 5;
			else
				sib |= (byte)(baseNum & 7);
			if (indexNum < 0)
				sib |= 0x20;
			else
				sib |= (byte)((indexNum & 7) << 3);
		}

		if (baseNum >= 0) {
			assert Integer.compareUnsigned(baseNum, 15) <= 0 : baseNum;// No '& 1' required below
			encoderFlags |= baseNum >>> 3;
		}
		if (indexNum >= 0) {
			encoderFlags |= (indexNum >>> 2) & 2;
			encoderFlags |= (indexNum & 0x10) << EncoderFlags.VVVVV_SHIFT;
			assert Integer.compareUnsigned(indexNum, 31) <= 0 : indexNum;
		}
	}

	private static final byte[] segmentOverrides = new byte[] { 0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65 };

	void writePrefixes(Instruction instruction) {
		writePrefixes(instruction, true);
	}

	void writePrefixes(Instruction instruction, boolean canWriteF3) {
		assert !handler.isSpecialInstr;
		int seg = instruction.getSegmentPrefix();
		if (seg != Register.NONE) {
			assert Integer.compareUnsigned(seg - Register.ES, segmentOverrides.length) < 0;
			writeByteInternal(segmentOverrides[seg - Register.ES]);
		}
		if ((encoderFlags & EncoderFlags.PF0) != 0 || instruction.getLockPrefix())
			writeByteInternal(0xF0);
		if ((encoderFlags & EncoderFlags.P66) != 0)
			writeByteInternal(0x66);
		if ((encoderFlags & EncoderFlags.P67) != 0)
			writeByteInternal(0x67);
		if (canWriteF3 && instruction.getRepePrefix())
			writeByteInternal(0xF3);
		if (instruction.getRepnePrefix())
			writeByteInternal(0xF2);
	}

	private void writeModRM() {
		assert !handler.isSpecialInstr;
		assert (encoderFlags & (EncoderFlags.MOD_RM | EncoderFlags.DISPL)) != 0 : encoderFlags;
		if ((encoderFlags & EncoderFlags.MOD_RM) != 0) {
			writeByteInternal(modRM);
			if ((encoderFlags & EncoderFlags.SIB) != 0)
				writeByteInternal(sib);
		}

		int diff4;
		displAddr = (int)currentRip;
		switch (displSize) {
		case DisplSize.NONE:
			break;

		case DisplSize.SIZE1:
			writeByteInternal(displ);
			break;

		case DisplSize.SIZE2:
			diff4 = displ;
			writeByteInternal(diff4);
			writeByteInternal(diff4 >>> 8);
			break;

		case DisplSize.SIZE4:
			diff4 = displ;
			writeByteInternal(diff4);
			writeByteInternal(diff4 >>> 8);
			writeByteInternal(diff4 >>> 16);
			writeByteInternal(diff4 >>> 24);
			break;

		case DisplSize.SIZE8:
			diff4 = displ;
			writeByteInternal(diff4);
			writeByteInternal(diff4 >>> 8);
			writeByteInternal(diff4 >>> 16);
			writeByteInternal(diff4 >>> 24);
			diff4 = displHi;
			writeByteInternal(diff4);
			writeByteInternal(diff4 >>> 8);
			writeByteInternal(diff4 >>> 16);
			writeByteInternal(diff4 >>> 24);
			break;

		case DisplSize.RIP_REL_SIZE4_TARGET32:
			int eip = (int)currentRip + 4 + immSizes[immSize];
			diff4 = displ - eip;
			writeByteInternal(diff4);
			writeByteInternal(diff4 >>> 8);
			writeByteInternal(diff4 >>> 16);
			writeByteInternal(diff4 >>> 24);
			break;

		case DisplSize.RIP_REL_SIZE4_TARGET64:
			long rip = currentRip + 4 + immSizes[immSize];
			long diff8 = (((long)displHi << 32) | ((long)displ & 0xFFFF_FFFFL)) - rip;
			if (diff8 < -0x8000_0000 || diff8 > 0x7FFF_FFFF)
				setErrorMessage(
						String.format("RIP relative distance is too far away: NextIP: 0x%16X target: 0x%8X%8X, diff = %d, diff must fit in an Int32",
								rip, displHi, displ, diff8));
			diff4 = (int)diff8;
			writeByteInternal(diff4);
			writeByteInternal(diff4 >>> 8);
			writeByteInternal(diff4 >>> 16);
			writeByteInternal(diff4 >>> 24);
			break;

		default:
			throw new UnsupportedOperationException();
		}
	}

	private void writeImmediate() {
		assert !handler.isSpecialInstr;
		short ip;
		int eip;
		long rip;
		short diff2;
		int diff4;
		long diff8;
		int value;
		immAddr = (int)currentRip;
		switch (immSize) {
		case ImmSize.NONE:
			break;

		case ImmSize.SIZE1:
		case ImmSize.SIZE_IB_REG:
		case ImmSize.SIZE1_OP_CODE:
			writeByteInternal(immediate);
			break;

		case ImmSize.SIZE2:
			value = immediate;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			break;

		case ImmSize.SIZE4:
			value = immediate;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(value >>> 16);
			writeByteInternal(value >>> 24);
			break;

		case ImmSize.SIZE8:
			value = immediate;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(value >>> 16);
			writeByteInternal(value >>> 24);
			value = immediateHi;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(value >>> 16);
			writeByteInternal(value >>> 24);
			break;

		case ImmSize.SIZE2_1:
			value = immediate;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(immediateHi);
			break;

		case ImmSize.SIZE1_1:
			writeByteInternal(immediate);
			writeByteInternal(immediateHi);
			break;

		case ImmSize.SIZE2_2:
			value = immediate;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			value = immediateHi;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			break;

		case ImmSize.SIZE4_2:
			value = immediate;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(value >>> 16);
			writeByteInternal(value >>> 24);
			value = immediateHi;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			break;

		case ImmSize.RIP_REL_SIZE1_TARGET16:
			ip = (short)((int)currentRip + 1);
			diff2 = (short)((short)immediate - (short)ip);
			if (diff2 < -0x80 || diff2 > 0x7F)
				setErrorMessage(String.format("Branch distance is too far away: NextIP: 0x%4X target: 0x%4X, diff = %d, diff must fit in an Int8", ip,
						immediate & 0xFFFF, diff2));
			writeByteInternal((int)diff2);
			break;

		case ImmSize.RIP_REL_SIZE1_TARGET32:
			eip = (int)currentRip + 1;
			diff4 = (int)immediate - (int)eip;
			if (diff4 < -0x80 || diff4 > 0x7F)
				setErrorMessage(String.format("Branch distance is too far away: NextIP: 0x%8X target: 0x%8X, diff = %d, diff must fit in an Int8",
						eip, immediate, diff4));
			writeByteInternal((int)diff4);
			break;

		case ImmSize.RIP_REL_SIZE1_TARGET64:
			rip = currentRip + 1;
			diff8 = (((long)immediateHi << 32) | ((long)immediate & 0xFFFF_FFFFL)) - rip;
			if (diff8 < -0x80 || diff8 > 0x7F)
				setErrorMessage(String.format("Branch distance is too far away: NextIP: 0x%16X target: 0x%8X%8X, diff = %d, diff must fit in an Int8",
						rip, immediateHi, immediate, diff8));
			writeByteInternal((int)diff8);
			break;

		case ImmSize.RIP_REL_SIZE2_TARGET16:
			eip = (int)currentRip + 2;
			value = immediate - eip;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			break;

		case ImmSize.RIP_REL_SIZE2_TARGET32:
			eip = (int)currentRip + 2;
			diff4 = (int)(immediate - eip);
			if (diff4 < -0x8000 || diff4 > 0x7FFF)
				setErrorMessage(String.format("Branch distance is too far away: NextIP: 0x%8X target: 0x%8X, diff = %d, diff must fit in an Int16",
						eip, immediate, diff4));
			value = (int)diff4;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			break;

		case ImmSize.RIP_REL_SIZE2_TARGET64:
			rip = currentRip + 2;
			diff8 = (((long)immediateHi << 32) | ((long)immediate & 0xFFFF_FFFFL)) - (long)rip;
			if (diff8 < -0x8000 || diff8 > 0x7FFF)
				setErrorMessage(
						String.format("Branch distance is too far away: NextIP: 0x%16X target: 0x%8X%8X, diff = %d, diff must fit in an Int16", rip,
								immediateHi, immediate, diff8));
			value = (int)diff8;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			break;

		case ImmSize.RIP_REL_SIZE4_TARGET32:
			eip = (int)currentRip + 4;
			value = immediate - eip;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(value >>> 16);
			writeByteInternal(value >>> 24);
			break;

		case ImmSize.RIP_REL_SIZE4_TARGET64:
			rip = currentRip + 4;
			diff8 = (((long)immediateHi << 32) | ((long)immediate & 0xFFFF_FFFFL)) - rip;
			if (diff8 < -0x8000_0000 || diff8 > 0x7FFF_FFFF)
				setErrorMessage(
						String.format("Branch distance is too far away: NextIP: 0x%16X target: 0x%8X%8X, diff = %d, diff must fit in an Int32", rip,
								immediateHi, immediate, diff8));
			value = (int)diff8;
			writeByteInternal(value);
			writeByteInternal(value >>> 8);
			writeByteInternal(value >>> 16);
			writeByteInternal(value >>> 24);
			break;

		default:
			throw new UnsupportedOperationException();
		}
	}

	/**
	 * Writes a byte to the output buffer
	 *
	 * @param value Value to write
	 */
	public void writeByte(byte value) {
		writeByteInternal(value);
	}

	void writeByteInternal(int value) {
		writer.writeByte((byte)value);
		currentRip++;
	}

	/**
	 * Gets the offsets of the constants (memory displacement and immediate) in the encoded instruction.
	 * The caller can use this information to add relocations if needed.
	 */
	public ConstantOffsets getConstantOffsets() {
		ConstantOffsets constantOffsets = new ConstantOffsets();

		switch (displSize) {
		case DisplSize.NONE:
			break;

		case DisplSize.SIZE1:
			constantOffsets.displacementSize = 1;
			constantOffsets.displacementOffset = (byte)(displAddr - eip);
			break;

		case DisplSize.SIZE2:
			constantOffsets.displacementSize = 2;
			constantOffsets.displacementOffset = (byte)(displAddr - eip);
			break;

		case DisplSize.SIZE4:
		case DisplSize.RIP_REL_SIZE4_TARGET32:
		case DisplSize.RIP_REL_SIZE4_TARGET64:
			constantOffsets.displacementSize = 4;
			constantOffsets.displacementOffset = (byte)(displAddr - eip);
			break;

		case DisplSize.SIZE8:
			constantOffsets.displacementSize = 8;
			constantOffsets.displacementOffset = (byte)(displAddr - eip);
			break;

		default:
			throw new UnsupportedOperationException();
		}

		switch (immSize) {
		case ImmSize.NONE:
		case ImmSize.SIZE_IB_REG:
		case ImmSize.SIZE1_OP_CODE:
			break;

		case ImmSize.SIZE1:
		case ImmSize.RIP_REL_SIZE1_TARGET16:
		case ImmSize.RIP_REL_SIZE1_TARGET32:
		case ImmSize.RIP_REL_SIZE1_TARGET64:
			constantOffsets.immediateSize = 1;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			break;

		case ImmSize.SIZE1_1:
			constantOffsets.immediateSize = 1;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			constantOffsets.immediateSize2 = 1;
			constantOffsets.immediateOffset2 = (byte)(immAddr - eip + 1);
			break;

		case ImmSize.SIZE2:
		case ImmSize.RIP_REL_SIZE2_TARGET16:
		case ImmSize.RIP_REL_SIZE2_TARGET32:
		case ImmSize.RIP_REL_SIZE2_TARGET64:
			constantOffsets.immediateSize = 2;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			break;

		case ImmSize.SIZE2_1:
			constantOffsets.immediateSize = 2;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			constantOffsets.immediateSize2 = 1;
			constantOffsets.immediateOffset2 = (byte)(immAddr - eip + 2);
			break;

		case ImmSize.SIZE2_2:
			constantOffsets.immediateSize = 2;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			constantOffsets.immediateSize2 = 2;
			constantOffsets.immediateOffset2 = (byte)(immAddr - eip + 2);
			break;

		case ImmSize.SIZE4:
		case ImmSize.RIP_REL_SIZE4_TARGET32:
		case ImmSize.RIP_REL_SIZE4_TARGET64:
			constantOffsets.immediateSize = 4;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			break;

		case ImmSize.SIZE4_2:
			constantOffsets.immediateSize = 4;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			constantOffsets.immediateSize2 = 2;
			constantOffsets.immediateOffset2 = (byte)(immAddr - eip + 4);
			break;

		case ImmSize.SIZE8:
			constantOffsets.immediateSize = 8;
			constantOffsets.immediateOffset = (byte)(immAddr - eip);
			break;

		default:
			throw new UnsupportedOperationException();
		}

		return constantOffsets;
	}
}
